package com.awLib.controls
{
	/**
	 *  GlossaryText.as
	 *  by Jonathan Rowny, jrowny@aboutweb.com 
	 *  2/25/2008 
	 * 
	 * 	GlossaryText is a Text component that highlights selected words and dispatches events with their definitions when clicked.
	 *  It is up to the user to decide what to do with these events.	
	 * 
	 * 
	 * 
	 * 
	 * 
	 **/ 
	import com.awLib.events.GlossaryTextEvent;
	import com.awLib.glossary.GlossaryTerm;
	
	import flash.events.TextEvent;
	
	import mx.controls.Text;
	import mx.styles.CSSStyleDeclaration;
	import mx.styles.StyleManager; 

 
	//This event fires when a word that's in the glossary is clicked
	[Event(name="termLink", type="com.awLib.events.GlossaryTextEvent")]
	
	//STYLES
	[Style(name="glossaryColor",type="uint", format="Color",inherit="no")]
	[Style(name="glossaryTextDecoration",type="boolean", enumeration="underline,normal",inherit="no")]
	[Style(name="glossaryFontWeight",type="string",enumeration="bold,normal")]
 
	 
	public class GlossaryText extends Text
	{
		/*
		 *  Properties
		 */
		private var _glossary:Array; //list of key/values or glossary/definitions
		private var glossaryChanged:Boolean; //used for commit props
		private var hiddenText:String; //we don't show text, only HTML text.
		private var textChanged:Boolean;
		private var glossaryStyleChanged:Boolean;
		private var _caseSensitive:Boolean=true;
		private var caseSensitiveChanged:Boolean=false;
		
		private static var defaultStyles:Boolean = buildDefaultStyles();
		
		/*
		 *  Constructor
		 */ 
		public function GlossaryText(){
			super(); 
			this.addEventListener(flash.events.TextEvent.LINK,onLinkClicked);
		}
		
		private static function buildDefaultStyles():Boolean{
			var style:CSSStyleDeclaration = StyleManager.getStyleDeclaration("GlossaryText");
			if(style){
				
				if(style.getStyle("glossaryTextDecoration") == undefined) style.setStyle("glossaryTextDecoration","underline");
				if(style.getStyle("glossaryFontWeight")==undefined) style.setStyle("glossaryFontWeight","bold");
			}else{
				style = new CSSStyleDeclaration();
				style.defaultFactory = function():void{
					this.glossaryColor = undefined;
					this.glossaryTextDecoration = "underline";
					this.glossaryFontWeight = "bold";
				}
				StyleManager.setStyleDeclaration("GlossaryText",style,true);
			}
			return true;
		}
		
		/*
		 *  Fun Stuff
		 */
		private function onLinkClicked(event:TextEvent):void{
			//kill this event! We want to use our own event instead. 
			//It's easier to kill an event rather than override TextField and then have to override Label as well to include that one
			this.dispatchEvent(new GlossaryTextEvent(lookupDefinition(event.text),true,false));
		}
		  
		//find the definition of a word in our glossary
		public function lookupDefinition(word:String):GlossaryTerm{
			for each(var term:GlossaryTerm in _glossary){
				if(term.word == word){
					return term;
					break;
				}				
			}
			return null;
		}
		
		public function addTerm(term:*,definition:String=null):void{
			//Make sure it's not a duplicate, if it is, check if definition is different, if it is, update it
			if(term is String){
				term = new GlossaryTerm(term as String,definition);				
			}else if(!(term is GlossaryTerm)){
				throw new Error("You must supply either a term/definition pair or a GlossaryTerm object");
			} 
			if(_glossary == null) _glossary = new Array();
			var duplicate:Boolean=false;
			for(var i:int=0;i<_glossary.length;i++){
				if(GlossaryTerm(_glossary[i]).word==term.word){
					duplicate=true;
					//update definition if different
					if(GlossaryTerm(_glossary[i]).definition!=term.definition){
						GlossaryTerm(_glossary[i]).definition=term.definition;
						//we don't really have to do this because highlighting isn't changing
						glossaryChanged=true;
						invalidateProperties();
						invalidateDisplayList();
					}
				}
			}
			if(!duplicate){
				if(_glossary==null)	_glossary = new Array();
				_glossary.push(term);
				glossaryChanged=true;
				invalidateProperties();
				invalidateDisplayList(); 
			} 
		}
		 
		private function parseGlossaryText():String{			
			var parsedText:String = hiddenText;
			if(parsedText!=null && parsedText.length>0){
				var preHTML:String = getHTMLFormatting("open");
				var postHTML:String = getHTMLFormatting("close");
				var regexProps:String = "g";
				
				if(!_caseSensitive)
					regexProps = "i|"+regexProps;	//if this is not case sensitive, add the "i" flag
					
				for each(var term:GlossaryTerm in _glossary){					
					parsedText = parsedText.replace(new RegExp(term.word,regexProps),preHTML+'<a href="event:'+term.word+'">$&</a>'+postHTML);
				}
				
				return parsedText;  
			}else{
				return "";
			}
		}  
		private function getHTMLFormatting(position:String):String{
			var result:String ="";
			if(position=="open"){				
				if(getStyle("glossaryColor")!=undefined) result+='<font color="#'+convertToRGB(getStyle("glossaryColor"))+'">';
				if(getStyle("glossaryTextDecoration") == "underline") result+="<u>";
				if(getStyle("glossaryFontWeight") == "bold") result+="<b>";
			}else{
				if(getStyle("glossaryFontWeight") == "bold") result+="</b>";
				if(getStyle("glossaryTextDecoration") == "underline") result+="</u>";				
				if(getStyle("glossaryColor")!=undefined) result+='</font>';
			}
			return result;
		}
		private function convertToRGB(color:uint):String {		
		    var str:String = color.toString(16).toUpperCase();
            str = String("000000" + str).substr(-6);
            return str;
		}
		/*
		 *  Getters/Setters 
		 */
		 
		public function set glossary(gloss:Array):void{
			if(gloss!=_glossary){				
				
				//enforce the type in our array, we only want glossary terms
				for each(var item:Object in gloss){
					if(!(item is GlossaryTerm)){
						try{
							item = item as GlossaryTerm; //try to cast it
						}catch(error:*){
							throw new Error("The glossary may only contain GlossaryTerm objects");
						}
					}
				}
				
				_glossary=gloss;
				glossaryChanged=true;
				invalidateProperties();
				invalidateDisplayList();
			}
		}
		public function get glossary():Array{
			return _glossary;
		}
		public function set caseSensitive(value:Boolean):void{
			if(_caseSensitive!=value){				
				
				_caseSensitive=value;
				caseSensitiveChanged=true;
				invalidateProperties();
				invalidateDisplayList();
			}
		}
		public function get caseSensitive():Boolean{
			return _caseSensitive;
		}
		
		/*
		 * Overrides!
		*/		
		override public function styleChanged(styleProp:String):void{
			super.styleChanged(styleProp);
			if(styleProp == "glossaryColor" || styleProp == "glossaryFontWeight" || styleProp == "glossaryTextDecoration"){
				glossaryStyleChanged=true;
				invalidateProperties();
			}
		}
		
		override public function set text(value:String):void{
			if(hiddenText!=value){
				hiddenText=value;
				textChanged=true;
				invalidateProperties();
			}			
		}
		
		override protected function commitProperties():void{			
			var newHTML:String;
			if(glossaryChanged || textChanged || glossaryStyleChanged || caseSensitiveChanged){		
				newHTML = parseGlossaryText()
				this.htmlText=newHTML;					
				this.textField.htmlText=newHTML;				
				glossaryChanged=false;
				glossaryStyleChanged=false;
				textChanged=false;
				caseSensitiveChanged=false;
			}
			super.commitProperties();
		} 
		
		
	}
}